/*
    Project:        Home Power Control
    File:           HPCServer.h
    Author:         christian@karunamusic.at
    Date:           01.2015
    Description:    The HPC Server.
 */

#include <QCommandLineOption>
#include <QCommandLineParser>
#include <QSettings>
#include <QCoreApplication>
#include <QFileInfo>
#include <QTextStream>
#include <QTcpSocket>
#include <QDomDocument>
#include <QDate>
#include <QTime>
#include <iostream>
#include "HPCRequest.h"
#include "HPCLogger.h"
#include "HPCValue.h"
#include "HPCSensors.h"
#include "HPCServer.h"
#include <a.out.h>

#define CLIENTNAME(c,n) \
QTcpSocket *c = qobject_cast<QTcpSocket*>(sender()); \
if ( !c ) return; \
QString n = clientNameFromSocket(c);

HPCServer::HPCServer(int& argc, char** argv)
    : HPCApplication(argc,argv,"Server"), m_Server(0), m_HPCDb(0)
{
    m_Server = new QTcpServer(this);
    connect(m_Server,SIGNAL(newConnection()),this,SLOT(newConnection()));
}

HPCServer::~HPCServer()
{
    if ( m_Server )
    {
        m_Server->close();
    }

    m_WriteTimer->stop();

    if ( m_HPCDb )
    {
        delete m_HPCDb;
    }
}

void HPCServer::Run()
{
    HPCApplication::Run();

    while ( m_Run )
    {
        processEvents();
    }

    quit();
}

void HPCServer::command( QString cmd )
{
    HPCApplication::command( cmd );
    QStringList argv = cmd.split(" ",QString::SkipEmptyParts);
    QTextStream cout(stdout);

    if ( m_Run )
    {
        if ( argv[0] == "exit" || argv[0] != "quit" || argv[0] == "loglevel" || argv[0] == "help"  )
        {
            // do nothing - handled in HPCApplication!
        }
        else
        {
            cout << "invalid command '" << cmd << "'!" << endl;
            LOG(warning,"Invalid command recieved: " << argv[0]);
        }
    }
}

void HPCServer::readConfig()
{
    LOG(info, "reading " << m_ConfigFile);
    HPCApplication::readConfig();

    QSettings cfg( m_ConfigFile, QSettings::IniFormat );

    QString database = cfg.value("connection/database").toString();
    QString host     = cfg.value("connection/host").toString();
    QString user     = cfg.value("connection/user").toString();
    QString password = cfg.value("connection/password").toString();
    QString hostName = cfg.value("connection/hpchostname").toString();
    unsigned int hpcPort  = cfg.value("connection/hpcport").toUInt();

    QDomDocument docCfg;
    QString appPath = applicationFilePath();

    m_HPCDb = new HPCDatabase (host, user, password, database, appPath);
    LOG(info, "Connected to database " << database << " on " << host << " as " << user);

    if (!m_HPCDb->getConfig(&docCfg)){
        LOG(info, "error while getting the configuration data!");
    }
    else {
        m_WriteTimer = new QTimer(this);
        connect(m_WriteTimer, SIGNAL(timeout()), this, SLOT(writeAverageValues()));
        m_WriteTimer->start(60000);			// every minute
        createClients (docCfg);
    }
    m_Server->listen(QHostAddress(hostName), hpcPort);
    LOG(info, "Listening on " << hostName << ":" << hpcPort);
}

void HPCServer::cliHelp( QString topic )
{
    HPCApplication::cliHelp();
    if ( topic == "loglevel" )
        return;

    QTextStream cout(stdout);
    if ( topic == "" )
    {
        // common help...
    }
}

void HPCServer::newConnection()
{
    QTcpSocket *cl = m_Server->nextPendingConnection();
    connect( cl,SIGNAL(disconnected()),      this,SLOT(disconnected()));
    connect( cl,SIGNAL(bytesWritten(qint64)),this,SLOT(bytesWritten(qint64)) );
    connect( cl,SIGNAL(readyRead()),         this,SLOT(readyRead()) );
    LOG(info,"new client connected.")
}

void HPCServer::disconnected()
{
    CLIENTNAME(cl,name);
    m_Clients[name]->connected = false;
    LOG(info,"client " << name << " disconnected.")
}

void HPCServer::bytesWritten(qint64 bytes)
{
    CLIENTNAME(cl,name);
    LOG(info,QString::number(bytes) << " bytes sent to client " << name << ".");
}

void HPCServer::readyRead()
{
    CLIENTNAME(cl,name);
    QByteArray ba = cl->readAll();
    HPCRequest req(ba);

    switch ( req.Type() )
    {
        case config:
        {
            name = "";
            req.Stream() >> name;
            if (m_Clients.contains(name))
            {
                if ( m_Clients[name]->Socket )
                {
                    delete m_Clients[name]->Socket;
                }
                m_Clients[name]->Socket = cl;
                m_Clients[name]->connected = true;
                LOG(info,"send configuration of " << m_Clients[name]->Sensors.size() << " sensors to client: " << name);
                HPCRequest ret(config);
                ret.Stream() << m_Clients[name]->Sensors;
                cl->write(ret);
            }
            else
            {
                LOG(warning,"unknown client: " << name);
            }
        }
        break;
        case valueChanged:
        {
            QString sname;
            QVariant var;

            req.Stream() >> sname >> var;
            if ( m_Clients.contains(name) && m_Clients[name]->Sensors.contains(sname) )
            {
                LOG(debug,name << "." << sname << " changed: " << var.toString());
                HPCSensor *sens = m_Clients[name]->Sensors[sname];
                HPCValue v(var);
                sens->setValue(v);
            }
            else
            {
                LOG(warning,"Sensor " << name << "." << sname << " not available!");
            }
        }
        break;
        case valueAlarmed:
        {
            QString sname;
            QVariant var;
            bool gt;
            unsigned int ndx;

            req.Stream() >> sname >> var >> gt >> ndx;
            LOG(warning,name << "." << sname << " alarmed: " << var.toString() << " (gt=" << gt << ", ndx=" << ndx << ")");
        }
        break;
        default:
        {
            LOG(error,"invalid request type "<< (unsigned int)req.Type() << " recieved!");
        }
        break;
    }
}

QString HPCServer::clientNameFromSocket( QTcpSocket *cl )
{
    QMapIterator<QString, HPCClientInfo*> i(m_Clients);
    while ( i.hasNext() )
    {
        i.next();
        if ( i.value()->Socket == cl )
            return i.key();
    }
    return QString();
}

QDataStream& operator<< ( QDataStream& os, QMap<QString,HPCSensor*>& smap )
{
    os << smap.size();

    QMapIterator<QString, HPCSensor*> i(smap);
    while ( i.hasNext() )
    {
        i.next();
        HPCSensor *s = i.value();
        HPCSensor::TSensorType t = s->Type();
        LOG(info,"streaming " << s->asString());
        switch ( t )
        {
            case HPCSensor::TSensorType::HPCButton:
            {
                os << *(dynamic_cast<HPCButton*>(s));
            }
            break;
            case HPCSensor::TSensorType::HPCLight:
            {
                os << *(dynamic_cast<HPCLight*>(s));
            }
            break;
            case HPCSensor::TSensorType::HPCAnemometer:
            {
                os << *(dynamic_cast<HPCAnemometer*>(s));
            }
            break;
            case HPCSensor::TSensorType::HPCRainGauge:
            {
                os << *(dynamic_cast<HPCRainGauge*>(s));
            }
            break;
            case HPCSensor::TSensorType::HPCWindVane:
            {
                os << *(dynamic_cast<HPCWindVane*>(s));
            }
            break;
            case HPCSensor::TSensorType::HPCHumidity22:
            {
                os << *(dynamic_cast<HPCHumidity22*>(s));
            }
            break;
            case HPCSensor::TSensorType::HPCTemperature22:
            {
                os << *(dynamic_cast<HPCTemperature22*>(s));
            }
            break;
            case HPCSensor::TSensorType::HPCBarometer:
            {
                os << *(dynamic_cast<HPCBarometer*>(s));
            }
            default:
            {
                os << (quint16)HPCSensor::TSensorType::undefined;
            }
        }
    }

    return os;
}

void HPCServer::createClients (QDomDocument doc)
{
    LOG(debug,"createClients Doc: " << doc)
    QDomElement el = doc.documentElement();
    QDomNode nCl = el.firstChild();


    while (!nCl.isNull()) {
        if(nCl.isElement()) {
            QDomElement eCl = nCl.toElement();
            QString clName = eCl.attribute("title");

           QDomNode nSens = eCl.firstChild();

            m_Clients.insert(clName,new HPCClientInfo());

            while (!nSens.isNull()) {
                if(nSens.isElement()) {
                    QDomElement eSens = nSens.toElement();

                    HPCSensor::TSensorType type = (HPCSensor::TSensorType)eSens.attribute("typeid").toUInt();
                    QString name        = eSens.attribute("title");
                    unsigned int id     = eSens.attribute("id" ).toUInt();
                    unsigned int pin    = eSens.attribute("pin").toUInt();

                    HPCSensor* sensor = 0;

                    switch ( type )
                    {
                        case HPCSensor::TSensorType::HPCButton:
                        {
                            sensor = new HPCButton(name,id,pin);
                        }
                        break;
                        case HPCSensor::TSensorType::HPCLight:
                        {
                            sensor = new HPCLight(name,id,pin);
                        }
                        break;
                        case HPCSensor::TSensorType::HPCAnemometer:
                        {
                            QSettings cfg( m_ConfigFile, QSettings::IniFormat );
                            QString entry = "timer/" + clName + "." + name;
                            unsigned int i = cfg.value(entry).toUInt();
                            sensor = new HPCAnemometer(name,id,pin,i);
                        }
                        break;
                        case HPCSensor::TSensorType::HPCRainGauge:
                        {
                            QSettings cfg( m_ConfigFile, QSettings::IniFormat );
                            QString entry = "timer/" + clName + "." + name;
                            unsigned int i = cfg.value(entry).toUInt();
                            sensor = new HPCRainGauge(name,id,pin,i);
                        }
                        break;
                        case HPCSensor::TSensorType::HPCWindVane:
                        {
                            unsigned int ch = eSens.attribute("channel").toUInt();
                            sensor = new HPCWindVane(name,id,ch,pin);
                        }
                        break;
                        case HPCSensor::TSensorType::HPCHumidity22:
                        {
                            sensor = new HPCHumidity22(name,id,pin);
                        }
                        break;
                        case HPCSensor::TSensorType::HPCTemperature22:
                        {
                            sensor = new HPCTemperature22(name,id,pin);
                        }
                        break;
                        case HPCSensor::TSensorType::HPCBarometer:
                        {
                            sensor = new HPCBarometer(name,id,pin);
                        }
                        break;
                        default:
                        {
                            LOG( warning,"invalid sensortype " << eSens.attribute("TypeId").toUInt() );
                        }
                    }

                    if ( sensor )
                    {
                        QString mUnit = eSens.attribute("measureunit");
                        sensor->MeasureUnit(mUnit);

                        QDomNode nAlert = eSens.firstChild();

                        while (!nAlert.isNull()) {
                            QDomElement eAlert = nAlert.toElement();
                            QString aName = eAlert.tagName();
                            QString min = eAlert.attribute("minvalue");
                            QString max = eAlert.attribute("maxvalue");

                            sensor->addAlarm(min,max);

                            LOG(debug,"Alert: " << aName);
                            nAlert = nAlert.nextSibling();
                        }
                        // connect to signals "changed" and "alarm" of the sensor.
                        connect( sensor, SIGNAL(changed     (HPCSensor*, HPCSensorValue&)),
                                 this  , SLOT  (ValueChanged(HPCSensor*, HPCSensorValue&)) );
                        connect( sensor, SIGNAL(alarmed     (HPCSensor*, HPCSensorValue&, bool, unsigned int)),
                                 this  , SLOT  (ValueAlarmed(HPCSensor*, HPCSensorValue&, bool, unsigned int)) );

                        LOG( debug, sensor->asString() << " created.");
                        m_Clients[clName]->Sensors.insert(sensor->Name(),sensor);
                        sSum s;
                        s.Count = 0;
                        s.dSum = 0;
                        m_AverageValues.insert(sensor->ID(),s);
                    }

                }
                nSens = nSens.nextSibling();
            }
        }
        nCl = nCl.nextSibling();
    }
}

void HPCServer::ValueChanged(HPCSensor* sensor, HPCSensorValue& val )
{
    LOG(info, "Sensor " << sensor->Name() << " changed: " << val.V().toString() << " " << sensor->MeasureUnit() );

    m_AverageValues[sensor->ID()].dSum = val.V().toDouble() + m_AverageValues[sensor->ID()].dSum;
    m_AverageValues[sensor->ID()].Count++;

    m_HPCDb->writeActValue(sensor->ID(),val);
}

void HPCServer::ValueAlarmed(HPCSensor* sensor, HPCSensorValue& val, bool bMax, unsigned int index)
{
    LOG(info, "Sensor " << sensor->Name() << " alarmed: " << val.V().toString() << " " << sensor->MeasureUnit() );

}

void HPCServer::writeAverageValues()
{
    QMapIterator<uint, sSum> i(m_AverageValues);
    QString ts = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
    QString line;

    while ( i.hasNext() )
    {
        i.next();
        if (i.value().Count > 0) {
//            LOG (info, "---- dSum: " << i.value().dSum << "  Count: " << i.value().Count << "  id: " << i.key());
            QString sv = QString::number(i.value().dSum / i.value().Count);
            sv.replace(",",".");
            line = line + "(" + QString::number(i.key()) + ", '" + sv + "','" + ts + "'), ";
            // intialize
            m_AverageValues[i.key()].Count = 0;
            m_AverageValues[i.key()].dSum = 0;
        }
    }
    if (line.length() > 0) {
        m_HPCDb->writeMeasureValues(line);
    }
}

